/* * CMISBox - Synchronize and share your files with your CMIS Repository * * Copyright (C) 2011 - Andrea Agili * * CMISBox is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * CMISBox is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with CMISBox. If not, see <http://www.gnu.org/licenses/>. * */ package com.github.cmisbox.persistence; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import org.apache.chemistry.opencmis.client.api.CmisObject; import org.apache.chemistry.opencmis.client.api.Document; import org.apache.chemistry.opencmis.client.api.Folder; import org.apache.chemistry.opencmis.client.api.ObjectType; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.DateTools; import org.apache.lucene.document.DateTools.Resolution; import org.apache.lucene.document.Field; import org.apache.lucene.document.Field.Index; import org.apache.lucene.document.Field.Store; import org.apache.lucene.index.CorruptIndexException; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.index.Term; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.TopDocs; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.util.Version; import com.github.cmisbox.core.Config; import com.github.cmisbox.core.Main; import com.github.cmisbox.core.Messages; import com.github.cmisbox.local.Watcher; import com.github.cmisbox.remote.CMISRepository; import com.github.cmisbox.ui.UI; public class Storage { public final static String indexFolderName = "indexes"; public static final String FIELD_ROOT = "root"; public static final String FIELD_VERSION = "version"; public static final String FIELD_ID = "id"; public static final String FIELD_TYPE = "type"; public static final String FIELD_PATH = "path"; public static final String FIELD_LOCAL_MODIFIED = "localModified"; public static final String FIELD_REMOTE_MODIFIED = "remoteModified"; public static final String TYPE_FOLDER = "folder"; public static final String TYPE_FILE = "file"; private static Storage instance = new Storage(); public static Storage getInstance() { return Storage.instance; } private Log log; private Config config; private Directory directory; private IndexWriter writer; private IndexReader reader; private Storage() { this.log = LogFactory.getLog(this.getClass()); this.config = Config.getInstance(); File file = new File(this.config.getConfigHome(), Storage.indexFolderName); if (!file.exists()) { file.mkdirs(); this.log.info(Messages.createdIndexFolder); } try { this.directory = FSDirectory.open(file); this.writer = new IndexWriter(this.directory, new IndexWriterConfig(Version.LUCENE_33, new StandardAnalyzer(Version.LUCENE_33))); this.reader = IndexReader.open(this.writer, false); List<String> roots = this.getRootPaths(); for (String root : roots) { Watcher.getInstance().addWatch(root); } } catch (Exception e) { this.log.fatal(e); Main.exit(1); } } public void add(File f, CmisObject obj) throws Exception { if (obj.getBaseTypeId().value().equals(ObjectType.FOLDER_BASETYPE_ID)) { this.add(f, (Folder) obj, false); } else { this.add(f, (Document) obj); } } public void add(File file, Document document) throws Exception { FileOutputStream fos = new FileOutputStream(file); InputStream is = document.getContentStream().getStream(); byte[] buffer = new byte[8192]; int r = 0; while ((r = is.read(buffer)) != -1) { fos.write(buffer, 0, r); } fos.close(); this.indexDocument(file, document); } public void add(File file, Folder folder, boolean root) throws Exception { file.mkdir(); this.indexFolder(file, folder, root); for (CmisObject o : folder.getChildren()) { if (o.getBaseTypeId().value().equals(ObjectType.FOLDER_BASETYPE_ID)) { File nf = new File(file, o.getName()); this.add(nf, (Folder) o, false); } else if (o.getBaseTypeId().value() .equals(ObjectType.DOCUMENT_BASETYPE_ID)) { File nf = new File(file, o.getName()); this.add(nf, (Document) o); } } } public void close() { try { this.writer.close(); } catch (Exception e) { this.log.error(e); } } public void commit() throws Exception { this.writer.commit(); } public void delete(StoredItem item, boolean deleteChildren) throws Exception { this.writer.deleteDocuments(new Term(Storage.FIELD_PATH, item.getPath() + (deleteChildren ? "*" : ""))); this.commit(); } public void deleteById(String id) throws Exception { StoredItem item = this.findById(id); if (item != null) { this.delete(item, true); } } public StoredItem findById(String id) throws Exception { id = id.split(";")[0]; Query query = new TermQuery(new Term(Storage.FIELD_ID, id)); TopDocs search = this.getSearcher().search(query, 1); if (search.totalHits == 0) { return null; } org.apache.lucene.document.Document d = this.reader .document(search.scoreDocs[0].doc); return new StoredItem(0, d.getFieldable(Storage.FIELD_ID), d.getFieldable(Storage.FIELD_TYPE), d.getFieldable(Storage.FIELD_PATH), d.getFieldable(Storage.FIELD_LOCAL_MODIFIED), d.getFieldable(Storage.FIELD_REMOTE_MODIFIED), d.getFieldable(Storage.FIELD_VERSION)); } public List<StoredItem> findByPath(String path) throws Exception { Query query = new TermQuery(new Term(Storage.FIELD_PATH, path)); TopDocs search = this.getSearcher().search(query, 99999); List<StoredItem> res = new ArrayList<StoredItem>(search.totalHits); for (int i = 0; i < search.totalHits; i++) { org.apache.lucene.document.Document d = this.reader .document(search.scoreDocs[i].doc); res.add(new StoredItem(i, d.getFieldable(Storage.FIELD_ID), d .getFieldable(Storage.FIELD_TYPE), d .getFieldable(Storage.FIELD_PATH), d .getFieldable(Storage.FIELD_LOCAL_MODIFIED), d .getFieldable(Storage.FIELD_REMOTE_MODIFIED), d .getFieldable(Storage.FIELD_VERSION))); } return res; } public Long getLastRemoteModification() { return null; } private String getNewPath(StoredItem storedItem, File file, StoredItem baseItem) { String newPathBase = file.getAbsolutePath().substring( Config.getInstance().getWatchParent().length()); return (newPathBase + storedItem.getPath().substring( baseItem.getPath().length())); } public List<String> getRootIds() throws Exception { return this.getRootsField(Storage.FIELD_ID); } private List<String> getRootPaths() throws Exception { return this.getRootsField(Storage.FIELD_PATH); } public List<String> getRootsField(String field) throws Exception { Query query = new TermQuery(new Term(Storage.FIELD_ROOT, "" + true)); TopDocs search = this.getSearcher().search(query, 99999); List<String> roots = new ArrayList<String>(); for (ScoreDoc sd : search.scoreDocs) { org.apache.lucene.document.Document doc = this.reader .document(sd.doc); roots.add(doc.get(field)); } return roots; } private IndexSearcher getSearcher() throws Exception { this.reader = this.reader.reopen(); return new IndexSearcher(this.reader); } private void index(StoredItem si) throws Exception { org.apache.lucene.document.Document ldoc = new org.apache.lucene.document.Document(); ldoc.add(new Field(Storage.FIELD_PATH, si.getPath(), Store.YES, Index.NOT_ANALYZED)); ldoc.add(new Field(Storage.FIELD_TYPE, si.getType(), Store.YES, Index.NOT_ANALYZED)); ldoc.add(new Field(Storage.FIELD_ID, si.getId(), Store.YES, Index.NOT_ANALYZED)); ldoc.add(new Field(Storage.FIELD_VERSION, si.getVersion(), Store.YES, Index.NOT_ANALYZED)); ldoc.add(new Field(Storage.FIELD_LOCAL_MODIFIED, DateTools .timeToString(si.getLocalModified(), Resolution.MILLISECOND), Store.YES, Index.NOT_ANALYZED)); ldoc.add(new Field(Storage.FIELD_REMOTE_MODIFIED, DateTools .timeToString(si.getRemoteModified(), Resolution.MILLISECOND), Store.YES, Index.NOT_ANALYZED)); this.writer.addDocument(ldoc); this.log.debug(String.format("Indexed %s", ldoc)); } private void indexDocument(File file, Document document) throws CorruptIndexException, IOException { org.apache.lucene.document.Document ldoc = new org.apache.lucene.document.Document(); ldoc.add(new Field(Storage.FIELD_PATH, file.getAbsolutePath() .substring(this.config.getWatchParent().length()), Store.YES, Index.NOT_ANALYZED)); ldoc.add(new Field(Storage.FIELD_TYPE, Storage.TYPE_FILE, Store.YES, Index.NOT_ANALYZED)); ldoc.add(new Field(Storage.FIELD_ID, document.getId().split(";")[0], Store.YES, Index.NOT_ANALYZED)); ldoc.add(new Field(Storage.FIELD_VERSION, document.getVersionLabel(), Store.YES, Index.NOT_ANALYZED)); ldoc.add(new Field(Storage.FIELD_LOCAL_MODIFIED, DateTools .timeToString(file.lastModified(), Resolution.MILLISECOND), Store.YES, Index.NOT_ANALYZED)); ldoc.add(new Field(Storage.FIELD_REMOTE_MODIFIED, DateTools .timeToString(document.getLastModificationDate() .getTimeInMillis(), Resolution.MILLISECOND), Store.YES, Index.NOT_ANALYZED)); this.writer.addDocument(ldoc); this.log.debug(String.format("Indexed %s", ldoc)); } private void indexFolder(File file, Folder folder, boolean root) throws CorruptIndexException, IOException { org.apache.lucene.document.Document ldoc = new org.apache.lucene.document.Document(); ldoc.add(new Field(Storage.FIELD_PATH, file.getAbsolutePath() .substring(this.config.getWatchParent().length()), Store.YES, Index.NOT_ANALYZED)); ldoc.add(new Field(Storage.FIELD_TYPE, Storage.TYPE_FOLDER, Store.YES, Index.NOT_ANALYZED)); ldoc.add(new Field(Storage.FIELD_ROOT, "" + root, Store.YES, Index.NOT_ANALYZED)); ldoc.add(new Field(Storage.FIELD_ID, folder.getId().split(";")[0], Store.YES, Index.NOT_ANALYZED)); ldoc.add(new Field(Storage.FIELD_LOCAL_MODIFIED, DateTools .timeToString(file.lastModified(), Resolution.MILLISECOND), Store.YES, Index.NOT_ANALYZED)); ldoc.add(new Field(Storage.FIELD_REMOTE_MODIFIED, DateTools .timeToString(folder.getLastModificationDate() .getTimeInMillis(), Resolution.MILLISECOND), Store.YES, Index.NOT_ANALYZED)); this.writer.addDocument(ldoc); this.log.debug(String.format("Indexed %s", ldoc)); } public void localUpdate(StoredItem item, File f, CmisObject obj) throws Exception { this.delete(item, false); if (f.isFile()) { this.indexDocument(f, (Document) obj); } else { for (StoredItem si : this.findByPath(item.getPath() + "*")) { this.delete(si, false); String path = this.getNewPath(si, f, item); si.setPath(path); this.index(si); } } } public void synchRemoteFolder(String id, String name) throws Exception { UI.getInstance().setStatus(UI.Status.SYNCH); Folder folder = CMISRepository.getInstance().getFolder(id); File destFolder = new File(this.config.getWatchParent(), name); if (destFolder.exists()) { this.log.error(Messages.synchAlreadyExisting); if (UI.getInstance().isAvailable()) { UI.getInstance().notify(Messages.synchAlreadyExisting); } } else { this.add(destFolder, folder, true); this.writer.commit(); Watcher.getInstance().addWatch( destFolder.getAbsolutePath().substring( Config.getInstance().getWatchParent().length())); UI.getInstance().notify( Messages.folder + " " + destFolder.getName() + " " + Messages.isSynchronized); } UI.getInstance().setStatus(UI.Status.OK); } }